package cn.enjoy.redisson.service.impl;

import cn.enjoy.redisson.entity.RedPacket;
import cn.enjoy.redisson.service.ILuaService;
import cn.enjoy.redisson.util.RedisUtil;
import cn.enjoy.tool.exception.ServiceException;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author K
 * @date 2024-09-03 21:35
 */
@Slf4j
@Service
public class LuaServiceImpl implements ILuaService {
    private final BigDecimal min = new BigDecimal("0.01");
    private final String m_redPacket = "redPacket";
    private final String k_totalNum = "totalNum";
    private final String k_remainNum = "remainNum";
    private final String k_amount = "amount";

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DefaultRedisScript<Boolean> casScript;


    /**
     * 二倍均值计算公式：2 * 剩余金额 / 剩余红包数。
     */
    @Override
    public List<RedPacket> getRedPacket(Integer num, BigDecimal amount, Integer threadCount) {
        setRedPacket(num, amount);

        CopyOnWriteArrayList<RedPacket> result = new CopyOnWriteArrayList<>();
        ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            threadPool.execute(() -> robRebPacket(result));
        }
//        calculate(3, 100);
        threadPool.shutdown();

        return result;
    }

    /**
     * cas方式实现
     */
    private void robRebPacket(CopyOnWriteArrayList<RedPacket> result) {
        while (true) {
            Map<Object, Object> map = redisUtil.hmget(m_redPacket);
            JSONObject redPacketJson = JSONObject.parseObject(JSONObject.toJSONString(map));
            Integer totalNum = redPacketJson.getInteger(k_totalNum);
            Integer remainNum = redPacketJson.getInteger(k_remainNum);
            BigDecimal amount = redPacketJson.getBigDecimal(k_amount);
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException("红包已被抢完");
            }

            BigDecimal redAmount = calculateOne(totalNum, remainNum, amount);
            BigDecimal remainAmount = amount.subtract(redAmount);
            List<String> keyList = Stream.of(m_redPacket, k_remainNum, k_amount).collect(Collectors.toList());
            Boolean success = redisTemplate.execute(casScript, keyList, amount.floatValue(), remainAmount.floatValue());
            if (Boolean.TRUE.equals(success)) {
                RedPacket redPacket = new RedPacket();
                redPacket.setAmount(redAmount);
                redPacket.setId(totalNum - remainNum + 1);
                result.add(redPacket);
                log.info("时间：{}\t第{}个人抢到红包金额为：{}", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN),
                        totalNum - remainNum + 1, redAmount);
                break;
            } else {
//                try {
//                    Thread.sleep(50L);
//                } catch (InterruptedException e) {
//                }
            }
        }
    }

    /**
     * 二倍均值计算公式：2 * 剩余金额 / 剩余红包数。
     *
     * @param totalNum
     * @param curNum
     * @param amount
     * @return
     */
    private BigDecimal calculateOne(Integer totalNum, Integer curNum, BigDecimal amount) {
        if (curNum <= 1) {
            return amount;
        }
        final BigDecimal two = new BigDecimal("2");
        final BigDecimal hundred = new BigDecimal("100");

        BigDecimal maxRedPeck = amount.multiply(two).divide(new BigDecimal(curNum), 2, RoundingMode.CEILING);

        final Random random = new Random();
        final int nextInt = random.nextInt(100);
        BigDecimal redpeck = new BigDecimal(nextInt).multiply(maxRedPeck).divide(hundred, 2, RoundingMode.FLOOR);

        if (redpeck.compareTo(min) < 0) {
            redpeck = min;
        }

        return redpeck;
    }

    private void calculate(Integer num, BigDecimal amount) {
        BigDecimal remain = amount.subtract(min.multiply(new BigDecimal(num)));
        final Random random = new Random();
        final BigDecimal hundred = new BigDecimal("100");
        final BigDecimal two = new BigDecimal("2");
        BigDecimal sum = BigDecimal.ZERO;
        BigDecimal redpeck;
        for (int i = 0; i < num; i++) {
            final int nextInt = random.nextInt(100);
            if (i == num - 1) {
                redpeck = remain;
            } else {
                // 此轮最大红包：2 * 剩余金额 / 剩余红包数。
                BigDecimal maxRedPeck = remain.multiply(two).divide(new BigDecimal(num).subtract(new BigDecimal(i)), 2, RoundingMode.CEILING);
                redpeck = new BigDecimal(nextInt).multiply(maxRedPeck).divide(hundred, 2, RoundingMode.FLOOR);
            }
            if (remain.compareTo(redpeck) > 0) {
                remain = remain.subtract(redpeck).setScale(2, RoundingMode.HALF_UP);
            } else {
                remain = BigDecimal.ZERO;
            }
            sum = sum.add(min.add(redpeck)).setScale(2, RoundingMode.HALF_UP);
            System.out.println("第" + (i + 1) + "个人抢到红包金额为：" + min.add(redpeck));
        }
        System.out.println("红包总额：" + sum);
    }

    @Override
    public void setRedPacket(Integer num, BigDecimal amount) {
        redisUtil.hset(m_redPacket, k_totalNum, num);
        redisUtil.hset(m_redPacket, k_remainNum, num);
        redisUtil.hset(m_redPacket, k_amount, amount.floatValue());
    }


}
